package logic

import (
	"context"
	"errors"
	"strconv"
	"time"

	"gorm.io/gorm"

	"laundry-service/app/client/internal/svc"
	"laundry-service/app/client/internal/types"
	"laundry-service/entity"

	"github.com/golang-jwt/jwt/v4"
	"github.com/zeromicro/go-zero/core/logx"
)

type UserLoginLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewUserLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserLoginLogic {
	return &UserLoginLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *UserLoginLogic) UserRegister(openId string, invitedId string) (*entity.User, *entity.UserAuth, error) {
	newUser, newUserAuth, err := l.CreateNewUser(openId)
	if err != nil {
		return nil, nil, err
	}
	// 如果存在邀请人参数 创建邀请记录
	_, _ = l.CreateInvitedRecord(newUser.ID, invitedId)
	return newUser, newUserAuth, nil
}

func (l *UserLoginLogic) responseUserLoginApi(loginedId int) (resp *types.UserLoginResp, err error) {
	now := time.Now().Unix()
	accessExpire := l.svcCtx.Config.Auth.AccessExpire
	jwtToken, err := l.GetJwtToken(l.svcCtx.Config.Auth.AccessSecret, now, l.svcCtx.Config.Auth.AccessExpire, int64(loginedId))
	if err != nil {
		return nil, err
	}
	resp = new(types.UserLoginResp)
	resp.AccessToken = jwtToken
	resp.ExpiresAt = now + accessExpire
	return resp, nil
}

func (l *UserLoginLogic) UserLogin(req *types.UserLoginReq) (resp *types.UserLoginResp, err error) {
	openId, err := l.isValidLoginCode(req.Code)
	if err != nil {
		return nil, err
	}
	var user *entity.User = nil

	userAuth, err := l.svcCtx.Db.GetUserAuthByAuthId(openId)

	if errors.Is(err, gorm.ErrRecordNotFound) {
		user, userAuth, err = l.UserRegister(openId, req.InvitedId)
		if err != nil {
			return nil, err
		}

		l.BindUserCoupon(user.ID)
	}

	if user == nil {
		user, err = l.svcCtx.Db.GetUserById(userAuth.UserId)
		if err != nil {
			return nil, err
		}
	}

	if user.State != 1 {
		return nil, errors.New("您的账户已被禁用，请联系管理员")
	}

	return l.responseUserLoginApi(user.ID)
}

func (l *UserLoginLogic) isValidLoginCode(code string) (string, error) {
	isValid, err := l.svcCtx.MiniProgram.GetAuth().Code2Session(code)
	if err != nil {
		return "", err
	}
	if isValid.ErrCode != 0 {
		return "", errors.New(isValid.ErrMsg)
	}
	return isValid.OpenID, nil
}

func (l *UserLoginLogic) CreateNewUser(openId string) (*entity.User, *entity.UserAuth, error) {
	user := new(entity.User)
	user.State = 1
	if err := l.svcCtx.Db.UserSave(user); err != nil {
		return nil, nil, err
	}

	userAuth := new(entity.UserAuth)
	userAuth.UserId = user.ID
	userAuth.AuthId = openId
	if err := l.svcCtx.Db.UserAuthSave(userAuth); err != nil {
		return nil, nil, err
	}
	return user, userAuth, nil
}

func (l *UserLoginLogic) CreateInvitedRecord(userId int, invitedId string) (*entity.UserInvite, error) {
	if invitedId == "" {
		logx.Error("invitedId is empty")
		return nil, errors.New("invitedId is empty")
	}

	iId, err := strconv.Atoi(invitedId)
	if err != nil {
		return nil, errors.New("invitedId format err, err:" + err.Error())
	}

	record := new(entity.UserInvite)
	record.UserId = userId
	record.InvitedId = iId
	if err := l.svcCtx.Db.UserInviteSave(record); err != nil {
		return nil, err
	}

	return record, nil
}

func (l *UserLoginLogic) BindUserCoupon(userId int) (*entity.UserCoupon, error) {
	couponId := l.svcCtx.Config.RegisterCoupon.CouponId
	coupon, err := l.svcCtx.Db.GetCouponById(couponId)
	if err != nil {
		return nil, err
	}
	if coupon.State == -1 {
		return nil, errors.New("该优惠券已禁用")
	}

	userCoupon := new(entity.UserCoupon)
	userCoupon.UserId = userId
	userCoupon.CouponId = couponId
	userCoupon.State = 0
	t := time.Now().AddDate(0, 0, coupon.Expired)
	userCoupon.ExpiredAt = t
	if err := l.svcCtx.Db.UserCouponSave(userCoupon); err != nil {
		logx.Error("UserCouponSave error:", err)
		return nil, err
	}
	return userCoupon, nil
}

func (l *UserLoginLogic) GetJwtToken(secretKey string, iat, seconds, userId int64) (string, error) {
	claims := make(jwt.MapClaims)
	claims["exp"] = iat + seconds
	claims["iat"] = iat
	claims["userId"] = userId
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims = claims
	return token.SignedString([]byte(secretKey))
}
